今天我想要更新遊戲主畫面的UI,讓玩家在進遊戲時會有start、setting、help這樣的選單畫面
因此,我打算透過多個Panel切換的方式進行主畫面、遊戲本體與設定畫面等等的畫面切換
首先我先開一個MainPanel,作為點開遊戲時呈現出來的主畫面
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JButton;
import java.awt.Dimension;
import java.awt.Color;
import java.awt.Font;
public class MenuPanel extends JPanel{
// 使用和遊戲畫面同樣的寬跟高
static final int PANEL_WIDTH = 800;
static final int PANEL_HEIGHT = 500;
// 展示文字
JLabel titleLabel;
// 建立三個功能按鈕
JButton startButton;
JButton settingButton;
JButton helpButton;
Font titleFont; // 標題的文字形式
Font selectFont; // 選項的文字形式
MenuPanel(){
titleFont = new Font("MV Boli", Font.BOLD, 30);
selectFont = new Font("Times New Roman", Font.PLAIN, 20);
this.setPreferredSize(new Dimension(PANEL_WIDTH, PANEL_HEIGHT));
this.setBackground(Color.darkGray);
this.setLayout(null);
titleLabel = new JLabel("Snake Game");
titleLabel.setFont(titleFont);
titleLabel.setForeground(Color.WHITE);
titleLabel.setBounds(100, 50, 600, 100);
startButton = new JButton("Start Game");
startButton.setFont(selectFont);
startButton.setBounds(300, 250, 200, 50);
settingButton = new JButton("Settings");
settingButton.setFont(selectFont);
settingButton.setBounds(300, 320, 200, 50);
helpButton = new JButton("Help");
helpButton.setFont(selectFont);
helpButton.setBounds(300, 390, 200, 50);
this.add(titleLabel);
this.add(startButton);
this.add(settingButton);
this.add(helpButton);
}
}
建立完成後,我本來的想法是使用點擊按鈕後開啟新視窗的方式,可是我又覺得這樣會降低使用者體驗(每按一個按鈕就會跳出一個新頁面),因此我向Gemini求助,他建議我可以使用CardLayout這個系統
於是,我開始學習如何使用CardLayout
我需要在我的程式入口點,也就是最初創建的GameFrame,將其設為一個「容器」,用這個frame作為呼叫各個panel的總管
要做到這點,我們需要在GameFrame中宣告一個虛擬的panel,而這個panel的用意就是呼叫我們需要的panel,可以其稱為「牌堆」,用來存放各種不同的Panel(也就是各個卡牌)
import javax.swing.JFrame;
import java.awt.CardLayout;
import javax.swing.JPanel;
public class GameFrame extends JFrame{
CardLayout cardLayout; // 宣告一個CardLayout物件,我們之後還會用到它
JPanel mainPanel; // 宣告一個mainPanel作為我們的牌堆,用來存放各個panel
// 然後對於現有的兩個Panel,各宣告出他們的物件
MenuPanel menuPanel;
GamePanel gamePanel;
GameFrame(){
cardLayout = new CardLayout();
mainPanel = new JPanel(cardLayout); // 給mainPanel傳入cardLayout參數
// 我們需要修改Panel的constructor參數,才能傳當前的GameFrame參數(連結到牌堆)
menuPanel = new MenuPanel(this);
gamePanel = new GamePanel(this);
// 將卡牌加入牌堆並給他們命名(JPanel panel, String name),之後要用對應名稱呼叫他們
mainPanel.add(menuPanel, "Menu");
mainPanel.add(gamePanel, "Game");
this.add(mainPanel);
this.setTitle("SnakeGame");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setResizable(false);
this.pack();
this.setVisible(true);
this.setLocationRelativeTo(null);
}
// 切換面板的method
public void showPanel(String panelName){
// 使用show,檢查在mainPanel(牌堆)中是否有正在搜尋的panel(卡牌),如果有就顯示在畫面上
cardLayout.show(mainPanel, panelName);
// 我們把GamePanel中的startGame()從constructor移除並設為public
// 當檢查到我們正在呼叫mainPanel中的"Game"時,才觸發GamePanel中的開始遊戲
if ("Game".equals(panelName)) {
gamePanel.startGame();
}
}
}
接下來我們修改GamePanel和MenuPanel
// 已知我們將每個panel的constructor都改為傳入一個GameFrame參數
public class GamePanel extends JPanel{
GameFrame gameFrame; // 新增一個gameFrame變數
// ...
GamePanel(GameFrame frame){
this.gameFrame = frame; // 接收傳入的參數
// ...
}
// 現在知道遊戲開始是由外部觸發的(當menuPanel呼叫"Game"),因此我們只需要修改gameOver
private void gameOver(){
// 遊戲結束時先初始化變數資料
gameFrame.showPanel("Menu");
// 使用gameFrame,呼叫showPanel method,告訴他我們要回到主畫面
}
}
public class MenuPanel extends JPanel{
GameFrame gameFrame;
// ...
MenuPanel(GameFrame frame){
this.gameFrame = frame;
// ...
startButton.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e){
gameFrame.showPanel("Game");
}
}); // 新增startButton的觸發事件,用Anonymous Inner Class傳入事件效果,呼叫Game卡牌
}
}
到這邊就可以發現,我們的邏輯基本上就是:
一開始到主畫面,可以點擊開始遊戲、設定與幫助
我們會宣告出好幾個不同的Panel,分別對應到各個不同的功能,而這些Panel則是幫忙分擔GamePanel的任務
這樣子GamePanel只需要專心完成他的"Game"(遊戲)任務,剩下的交給不同的組件即可
而這個切換的過程則是透過CardLayout進行無縫切換
因此我今天學會如何透過這個手段增加使用者體驗之後,明天我要做的就是完善其他不同的Panel,包含SettingPanel、HelpPanel、GameOverPanel等,畢竟現在只有完成主畫面與遊戲本體的切換,還沒有設定幫助與結束頁面!